////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.chartClasses
{

import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.geom.Rectangle;
import mx.collections.ArrayCollection;
import mx.collections.ICollectionView;
import mx.collections.IList;
import mx.collections.IViewCursor;
import mx.collections.ListCollectionView;
import mx.collections.XMLListCollection;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;

/**
 *  The ChartElement class is the base class for visual chart elements.
 */
public class ChartElement extends DualStyleObject implements IChartElement
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	private static var nextID:uint = 0;

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	private static function generateGlyphID():uint
	{
		return nextID++;
	}

	//--------------------------------------------------------------------------
	//
	//  Constructor 
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Constructor.
	 */
	public function ChartElement()
	{
		super();

		_glyphID = generateGlyphID();
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	private var _userDataProvider:Object;
	
	/**
	 *  @private
	 */
	private var _glyphID:uint;
	
	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  chart
	//----------------------------------

	/**
	 *  Refers to the chart component containing this element.
	 */	
	protected function get chart():ChartBase
	{
		var p:DisplayObject = parent;

		while (!(p is ChartBase) && p != null)
		{
			p = p.parent;
		}	
				
		return p as ChartBase;
	}

	//----------------------------------
	//  chartDataProvider
	//----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The data provider assigned to the enclosing chart.
	 *  Element types can choose to inherit the data provider
	 *  from the enclosing chart if necessary, or allow developers
	 *  to assign data providers specifically to the element.
	 *  Not all elements are necessarily driven by a data provider.
	 */
	public function set chartDataProvider(value:Object):void
	{
		if (_userDataProvider == null)
			processNewDataProvider(value);
	}
	
	//----------------------------------
	//  cursor
	//----------------------------------

	/**
	 *  Each ChartElement carries a cursor associated with their dataProvider
	 *  for their own internal use.
	 *  ChartElements have sole ownership of this cursor;
	 *  they can assume no other code will modify its position.
	 */
	protected var cursor:IViewCursor;

	//----------------------------------
	//  dataProvider
	//----------------------------------

	/**
	 *  @private
	 */
	private var _dataProvider:ICollectionView;
	
	[Inspectable]

	/**
	 *  A data provider assigned to the this specific element.
	 *  In general, elements inherit the dataProvider from the enclosing chart.
	 *	But individual elements can override with a specific dataProvider
	 *  of their own.
	 */
	public function get dataProvider():Object
	{
		return _dataProvider;
	}

	/**
	 *  @private
	 */
	public function set dataProvider(value:Object):void
	{
		_userDataProvider = value;
		processNewDataProvider(value);
	}

	//----------------------------------
	//  dataTransform
	//----------------------------------

	/**
	 *  @private
	 */
	private var _dataTransform:DataTransform;

	[Inspectable(environment="none")]
	
	/**
	 *  The data dataTransform the element is associated with.
	 *  A data dataTransform represents an association between a set of elements
	 *  and a set of axis objects used to dataTransform those elements
	 *  from data space to screen coordinates and back.
	 *  A chart element uses its associated dataTransform to calculate
	 *  how to render its data.
	 */
	public function get dataTransform():DataTransform
	{
		return _dataTransform;
	}

	/**
	 *  @private
	 */
	public function set dataTransform(value:DataTransform):void
	{
		_dataTransform = value;

		for (var i:uint=0;i<numChildren;i++)
		{
			var g:DisplayObject = getChildAt(i);
			if (_dataTransform != null && g is IChartElement)
				IChartElement(g).dataTransform = _dataTransform;
		}

		if (_dataTransform)
			_dataTransform.dataChanged();

		invalidateDisplayList();
	}

	//----------------------------------
	//  labelContainer
	//----------------------------------

	[Inspectable(environment="none")]
	
	/**
	 *  @inheritDoc
	 */
	public function get labelContainer():Sprite	
	{
		return null;
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------

	/**
	 *  @inheritDoc
	 */
	override public function addChild(child:DisplayObject):DisplayObject
	{
		super.addChild(child);
		
		if (_dataTransform != null && child is IChartElement)
			IChartElement(child).dataTransform = _dataTransform;

		return child;
	}

	/**
	 * Adds a child DisplayObject instance to this DisplayObjectContainer 
	 * instance. The child is added
	 * at the index position specified. An index of 0 represents the back (bottom) 
	 * of the display list for this DisplayObjectContainer object.
	 * 
	 * <p>If you add a child object that already has a different display object container as
	 * a parent, the object is removed from the child list of the other display object container. </p>
	 *
	 * @return The DisplayObject instance that you pass in the 
	 * <code>child</code> parameter.
	 *
         * @param child The DisplayObject instance to add as a child of this 
	 * DisplayObjectContainer instance.
	 * 
         * @param index The index position to which the child is added. If you specify a 
	 * currently occupied index position, the child object that exists at that position and all
	 * higher positions are moved up one position in the child list. 
         *
	 */
	override public function addChildAt(child:DisplayObject,
										index:int):DisplayObject
	{
		super.addChildAt(child, index);
		
		if (_dataTransform != null && child is IChartElement)
			IChartElement(child).dataTransform = _dataTransform;

		return child;
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/** 
	 *  Called when a new dataProvider is assigned to the element.
	 *  Subclassers can override and define custom behavior
	 *  when a new dataProvider is assigned.
	 *  This method will be called when either the dataProvider property
	 *  is set, or when the chartDataProvider property is set
	 *  if no specific dataProvider has been assigned directly.
	 */
	protected function processNewDataProvider(value:Object):void
	{
		if (_dataProvider != null)
		{
			_dataProvider.removeEventListener(
				CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
		}

		if (value is Array)
		{
			value = new ArrayCollection(value as Array);
		}
		else if (value is ICollectionView)
		{
		}
		else if (value is IList)
		{
			value = new ListCollectionView(IList(value));
		}
		else if (value is XMLList)
		{
			value = new XMLListCollection(XMLList(value));
		}
		else if (value != null)
		{
			value = new ArrayCollection([ value ]);
		}
		else
		{
			value = new ArrayCollection();
		}

		_dataProvider = ICollectionView(value);

		cursor = _dataProvider.createCursor();

		if (_dataProvider != null) //[Matt] not sure this would ever be null
		{
			// Weak listeners to collections and dataproviders.
			_dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE,
				collectionChangeHandler, false, 0, true);
		}

		dataChanged();
	}

	/**
	 *  Called when the mapping of one or more associated axes changes.
	 *  The DataTransform assigned to this ChartElement will call this method
	 *  when any of the axes it represents is modified in some way.
	 */
	public function mappingChanged():void
	{
	}

	/**
	 *  Indicates the underlying data represented by the element has changed.
	 *  You should call this method whenever the data your series or element
	 *  is displaying changes.  
	 *  It gives any associated axes a chance to update their ranges
	 *  if appropriate.
	 */
	protected function dataChanged():void
	{

		invalidateDisplayList();
		if (_dataTransform)
			_dataTransform.dataChanged();
	}

	/** 
	 *  Finds the closest data point represented by the element
	 *  under the given coordinates.
	 *
	 *  <p>This method returns either an array of  HitData structures
	 *  describing the datapoints within range.
	 *  Individual ChartElements may choose to only return a single dataPoint
	 *  if their dataPoints are guaranteed not to overlap.</p>
 	 *
	 *  @param x The X coordinate.
	 *
	 *  @param y The Y coordinate.
	 *
	 *  @param sensitivity2 How close, in pixels, the pointer must be
	 *  to the exact coordinates to be considered "under".
	 *  This property is similar to the value of the mouseSensitivity
	 *  property of the chart control.
	 */
	public function findDataPoints(x:Number, y:Number,
								   sensitivity2:Number):Array
	{
		var result:Array = [];

		for (var i:int = numChildren - 1; i >= 0; i--)
		{
			var g:IChartElement = getChildAt(i) as IChartElement;
			if (g == null || g.visible == false)
				continue;
				
			var hds:Array = g.findDataPoints(x, y, sensitivity2);
			if (hds.length == 0)
				continue;

			result = result.concat(hds);
		}	
		
		return result;
	}
	
	/**
	 *  Creates a unique id to represent the a dataPoint
	 *  for comparison purposes.
	 *  Derived classes can call this function with a locally unique
	 *  data point ID to generate an ID that is unique across the application.
	 */
	protected function createDataID(dataPointID:Number):Number
	{
		return (_glyphID << 16) + dataPointID;
	}

	/**
	 *  @inheritDoc
	 */
	public function describeData(dimension:String,
								 requiredFields:uint):Array
	{
		return [];
	}
	
	/**
	 *  @inheritDoc
	 */
	public function chartStateChanged(oldState:uint, v:uint):void
	{
		var n:int = numChildren;
		for (var i:int = 0; i < n; i++)
		{
			var c:IChartElement = getChildAt(i) as IChartElement;
			if (c)
				c.chartStateChanged(oldState, v);
		}
	}

	/**
	 *  @inheritDoc
	 */
	public function collectTransitions(chartState:Number,
									   transitions:Array):void
	{
		var n:int = numChildren;
		for (var i:int = 0; i < n; i++)
		{
			var c:IChartElement = getChildAt(i) as IChartElement;
			if (c)
				c.collectTransitions(chartState, transitions);
		}
	}

	/**
	 *  @inheritDoc
	 */
	public function claimStyles(styles:Array, firstAvailable:uint):uint
	{
		return firstAvailable;
	}

	//--------------------------------------------------------------------------
	//
	//  Event handlers
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private function collectionChangeHandler(event:CollectionEvent = null):void
	{
		if (event && event.kind == CollectionEventKind.RESET)
			cursor = _dataProvider.createCursor();

		dataChanged();
	}
}

}
